// Redis客户端包, 提供Redis数据库连接和操作
// 本包设计采用接口隔离原则, 将Redis客户端和数据库操作分为两个接口
// 这样设计的好处是：
// 1. 更好的关注点分离, Client负责连接管理, Database负责数据操作
// 2. 便于测试, 可以轻松Mock这些接口
// 3. 便于扩展, 可以添加新的操作而不影响现有代码
package redis

import (
	"context"
	"time"

	"github.com/redis/go-redis/v9"
)

// Client Redis客户端接口
// 该接口主要负责Redis连接的生命周期管理和基础操作
type Client interface {
	// Ping 测试Redis连接是否正常
	Ping(ctx context.Context) error
	// Close 关闭Redis连接, 释放资源
	Close() error
	// Database 获取数据库操作接口, 用于执行数据操作
	Database() Database
}

// Database Redis数据库操作接口
// 该接口定义了Redis的各种数据操作方法, 包括字符串、列表、哈希表和JSON操作
type Database interface {
	// 字符串操作 - Redis 最基本的数据类型
	// Set 设置键值对, 可选过期时间
	Set(ctx context.Context, key string, value any, expiration time.Duration) error
	// Get 获取键值, 如果键不存在会返回redis.Nil错误
	Get(ctx context.Context, key string) (string, error)
	// GetDel 获取键值并删除键, 原子操作, Redis 6.2.0及以上版本支持
	GetDel(ctx context.Context, key string) (string, error)

	// 列表操作 - Redis列表是简单的字符串列表, 按照插入顺序排序
	// LPush 从列表左侧插入元素, 如果列表不存在则创建新列表
	LPush(ctx context.Context, key string, values ...any) (int64, error)
	// RPush 从列表右侧插入元素, 如果列表不存在则创建新列表
	RPush(ctx context.Context, key string, values ...any) (int64, error)
	// LRange 获取列表指定范围内的元素, 0表示第一个元素, -1表示最后一个元素
	LRange(ctx context.Context, key string, start, stop int64) ([]string, error)

	// 哈希表操作 - Redis哈希是字段和值之间的映射表
	// HSet 设置哈希表字段值, 如果哈希表不存在则创建
	HSet(ctx context.Context, key string, values ...any) (int64, error)
	// HGet 获取哈希表字段值, 如果字段不存在会返回redis.Nil错误
	HGet(ctx context.Context, key, field string) (string, error)
	// HGetAll 获取哈希表所有字段值, 返回字段-值的映射
	HGetAll(ctx context.Context, key string) (map[string]string, error)

	// 集合操作 - Redis集合是不重复元素的集合
	// SAdd 向集合添加一个或多个成员，返回添加成功的元素数
	SAdd(ctx context.Context, key string, members ...any) error
	// SRem 从集合中移除一个或多个成员，返回移除成功的元素数
	SRem(ctx context.Context, key string, members ...any) error
	// SMembers 获取集合中的所有成员
	SMembers(ctx context.Context, key string) ([]string, error)
	// SCard 获取集合中成员的数量
	SCard(ctx context.Context, key string) (int64, error)

	// JSON操作 - 通过RedisJSON模块操作JSON数据
	// JSONSet 设置JSON值, path为JSON路径, .表示根节点
	JSONSet(ctx context.Context, key, path string, value any) (string, error)
	// JSONGet 获取JSON值, 可指定多个路径
	JSONGet(ctx context.Context, key string, paths ...string) (string, error)
	// JSONMGet 获取多个键的JSON值, 使用相同的路径
	JSONMGet(ctx context.Context, path string, keys ...string) ([]any, error)
	// JSONArrAppend 向JSON数组追加元素
	JSONArrAppend(ctx context.Context, key, path string, values ...any) (int64, error)

	// 通用操作 - 适用于所有Redis数据类型
	// Del 删除一个或多个键
	Del(ctx context.Context, keys ...string) (int64, error)
	// Exists 检查一个或多个键是否存在, 返回存在的键数量
	Exists(ctx context.Context, keys ...string) (int64, error)
	// Expire 设置键的过期时间
	Expire(ctx context.Context, key string, expiration time.Duration) (bool, error)
}

// ClientImpl Redis客户端实现
// ClientImpl负责连接管理：
// 创建和维护Redis连接
// 测试连接可用性(Ping)
// 关闭连接(Close)
// 提供获取Database操作接口的方法
type ClientImpl struct {
	client *redis.Client // 原始Redis客户端
}

// DatabaseImpl Redis数据库操作实现
// DatabaseImpl负责数据操作：
// 实现各种Redis数据类型的操作方法
// 不负责连接的生命周期管理
type DatabaseImpl struct {
	client *redis.Client // 原始Redis客户端
}

// NewClient 创建新的Redis客户端
// 参数:
//   - redisURI: Redis连接URI, 格式为 redis://[username:password@]host[:port][/database]
//
// 返回:
//   - Client: Redis客户端接口
//   - error: 连接错误
func NewClient(redisURI string) (Client, error) {
	// 解析Redis URI并创建客户端选项
	opt, err := redis.ParseURL(redisURI)
	if err != nil {
		return nil, err
	}

	// 创建Redis客户端
	client := redis.NewClient(opt)

	// 返回客户端实现
	return &ClientImpl{
		client: client,
	}, nil
}

// Ping 测试Redis连接
// 参数:
//   - ctx: 上下文, 可用于超时控制
//
// 返回:
//   - error: 连接错误, nil表示连接正常
func (c *ClientImpl) Ping(ctx context.Context) error {
	return c.client.Ping(ctx).Err()
}

// Close 关闭Redis连接
// 返回:
//   - error: 关闭错误, nil表示正常关闭
func (c *ClientImpl) Close() error {
	return c.client.Close()
}

// Database 获取数据库操作接口
// 返回:
//   - Database: Redis数据库操作接口
func (c *ClientImpl) Database() Database {
	return &DatabaseImpl{
		client: c.client,
	}
}

// Set 设置键值对
// 参数:
//   - ctx: 上下文, 可用于超时控制
//   - key: 键
//   - value: 值, 可以是字符串或实现String()方法的类型
//   - expiration: 过期时间, 0表示永不过期
//
// 返回:
//   - error: 操作错误, nil表示成功
func (d *DatabaseImpl) Set(ctx context.Context, key string, value any, expiration time.Duration) error {
	return d.client.Set(ctx, key, value, expiration).Err()
}

// Get 获取键值
// 参数:
//   - ctx: 上下文
//   - key: 键
//
// 返回:
//   - string: 值
//   - error: 操作错误, 如果键不存在返回redis.Nil
func (d *DatabaseImpl) Get(ctx context.Context, key string) (string, error) {
	return d.client.Get(ctx, key).Result()
}

// GetDel 获取键值并删除键
// 参数:
//   - ctx: 上下文
//   - key: 键
//
// 返回:
//   - string: 值
//   - error: 操作错误, 如果键不存在返回redis.Nil
func (d *DatabaseImpl) GetDel(ctx context.Context, key string) (string, error) {
	return d.client.GetDel(ctx, key).Result()
}

// LPush 从列表左侧插入元素
// 参数:
//   - ctx: 上下文
//   - key: 列表键
//   - values: 要插入的值, 可以有多个
//
// 返回:
//   - int64: 操作后列表长度
//   - error: 操作错误
func (d *DatabaseImpl) LPush(ctx context.Context, key string, values ...any) (int64, error) {
	return d.client.LPush(ctx, key, values...).Result()
}

// RPush 从列表右侧插入元素
// 参数:
//   - ctx: 上下文
//   - key: 列表键
//   - values: 要插入的值, 可以有多个
//
// 返回:
//   - int64: 操作后列表长度
//   - error: 操作错误
func (d *DatabaseImpl) RPush(ctx context.Context, key string, values ...any) (int64, error) {
	return d.client.RPush(ctx, key, values...).Result()
}

// LRange 获取列表范围内的元素
// 参数:
//   - ctx: 上下文
//   - key: 列表键
//   - start: 起始位置, 0表示第一个元素
//   - stop: 结束位置, -1表示最后一个元素
//
// 返回:
//   - []string: 元素列表
//   - error: 操作错误
func (d *DatabaseImpl) LRange(ctx context.Context, key string, start, stop int64) ([]string, error) {
	return d.client.LRange(ctx, key, start, stop).Result()
}

// HSet 设置哈希表字段值
// 参数:
//   - ctx: 上下文
//   - key: 哈希表键
//   - values: 字段值对, 格式为field1, value1, field2, value2, ...
//
// 返回:
//   - int64: 添加的字段数量
//   - error: 操作错误
func (d *DatabaseImpl) HSet(ctx context.Context, key string, values ...any) (int64, error) {
	return d.client.HSet(ctx, key, values...).Result()
}

// HGet 获取哈希表字段值
// 参数:
//   - ctx: 上下文
//   - key: 哈希表键
//   - field: 字段名
//
// 返回:
//   - string: 字段值
//   - error: 操作错误, 如果字段不存在返回redis.Nil
func (d *DatabaseImpl) HGet(ctx context.Context, key, field string) (string, error) {
	return d.client.HGet(ctx, key, field).Result()
}

// HGetAll 获取哈希表所有字段值
// 参数:
//   - ctx: 上下文
//   - key: 哈希表键
//
// 返回:
//   - map[string]string: 字段名到字段值的映射
//   - error: 操作错误
func (d *DatabaseImpl) HGetAll(ctx context.Context, key string) (map[string]string, error) {
	return d.client.HGetAll(ctx, key).Result()
}

// JSONSet 设置JSON值
// 参数:
//   - ctx: 上下文
//   - key: JSON文档键
//   - path: JSON路径, .表示根节点
//   - value: 要设置的JSON值, 通常为JSON字符串
//
// 返回:
//   - string: 操作结果, 通常为"OK"
//   - error: 操作错误
func (d *DatabaseImpl) JSONSet(ctx context.Context, key, path string, value any) (string, error) {
	return d.client.JSONSet(ctx, key, path, value).Result()
}

// JSONGet 获取JSON值
// 参数:
//   - ctx: 上下文
//   - key: JSON文档键
//   - paths: JSON路径, 可以有多个
//
// 返回:
//   - string: JSON字符串
//   - error: 操作错误
func (d *DatabaseImpl) JSONGet(ctx context.Context, key string, paths ...string) (string, error) {
	return d.client.JSONGet(ctx, key, paths...).Result()
}

// JSONMGet 获取多个键的JSON值
// 参数:
//   - ctx: 上下文
//   - path: JSON路径
//   - keys: JSON文档键, 可以有多个
//
// 返回:
//   - []any: JSON值列表
//   - error: 操作错误
func (d *DatabaseImpl) JSONMGet(ctx context.Context, path string, keys ...string) ([]any, error) {
	return d.client.JSONMGet(ctx, path, keys...).Result()
}

// JSONArrAppend 向JSON数组追加元素
// 参数:
//   - ctx: 上下文
//   - key: JSON文档键
//   - path: JSON路径, 必须指向一个数组
//   - values: 要追加的值, 可以有多个
//
// 返回:
//   - int64: 操作后数组长度
//   - error: 操作错误
func (d *DatabaseImpl) JSONArrAppend(ctx context.Context, key, path string, values ...any) (int64, error) {
	result, err := d.client.JSONArrAppend(ctx, key, path, values...).Result()
	if err != nil {
		return 0, err
	}

	// JSONArrAppend返回的是每个路径的数组长度，我们只取第一个结果
	if len(result) > 0 {
		return result[0], nil
	}

	return 0, nil
}

// Del 删除键
// 参数:
//   - ctx: 上下文
//   - keys: 要删除的键, 可以有多个
//
// 返回:
//   - int64: 删除的键数量
//   - error: 操作错误
func (d *DatabaseImpl) Del(ctx context.Context, keys ...string) (int64, error) {
	return d.client.Del(ctx, keys...).Result()
}

// Exists 检查键是否存在
// 参数:
//   - ctx: 上下文
//   - keys: 要检查的键, 可以有多个
//
// 返回:
//   - int64: 存在的键数量
//   - error: 操作错误
func (d *DatabaseImpl) Exists(ctx context.Context, keys ...string) (int64, error) {
	return d.client.Exists(ctx, keys...).Result()
}

// Expire 设置键过期时间
// 参数:
//   - ctx: 上下文
//   - key: 键
//   - expiration: 过期时间
//
// 返回:
//   - bool: 是否设置成功, 如果键不存在则返回false
//   - error: 操作错误
func (d *DatabaseImpl) Expire(ctx context.Context, key string, expiration time.Duration) (bool, error) {
	return d.client.Expire(ctx, key, expiration).Result()
}
